package com.ihy.domain;

import lombok.*;
import org.hibernate.annotations.GenericGenerator;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import javax.persistence.*;
import java.io.Serializable;
import java.util.*;

/**
 * 用户表实体类
 * Entity注解：
 * 指定类是实体。此注解应用于实体类。
 * <p>
 * Table注解:
 * 指定带注解实体的主表。可以使用SecondaryTable或SecondaryTables注解指定其他表。
 * 如果没有为实体类指定表注解，则应用默认值。
 * <p>
 * Getter注解:
 * 创建当前实体类所有的get方法
 * <p>
 * Setter注解:
 * 创建当前实体类所有的set方法
 * <p>
 * ToString注解:
 * 创建当前实体类所有属性的toString方法
 * <p>
 * AllArgsConstructor注解：
 * 生成all args构造函数。all args构造函数要求类中的每个字段都有一个参数。
 * <p>
 * NoArgsConstructor注解：
 * 生成无参数构造函数。如果由于final字段的存在而无法编写此类构造函数，则将生成错误消息。
 * <p>
 * Component注解：
 * 指示带注解的类是“组件”。当使用基于注解的配置和类路径扫描时，这些类被认为是自动检测的候选类。
 * 其他类级注解也可以被视为标识组件，通常是一种特殊类型的组件：例如@Repository注解或AspectJ的@Aspect注解。
 */
@Entity
@Table(name = "user")
@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User implements Serializable, UserDetails {
    /**
     * Id注解：
     * 指定实体的主键。应用Id注解的字段或属性应该是以下类型之一：任何Java基元类型；任何基元包装类型；字符串；日期类型; java.sql.Date日期; java.math.BigDecimal文件; java.math.BigInteger文件.
     * 假定实体主键的映射列是主表的主键。如果未指定列注解，则假定主键列名是主键属性或字段的名称。
     */
    @Id
    //以泛型（反类型化）方式描述任何类型的Hibernate生成器的生成器注解
    @GenericGenerator(name = "pk", strategy = "increment")
     /*
     GeneratedValue注解：
     提供主键值的生成策略的规范。
     GeneratedValue注解可与Id注解一起应用于实体或映射超类的主键属性或字段。
     仅要求简单主键支持使用GeneratedValue注解。派生主键不支持使用GeneratedValue注解。

     Column注解：
     指定持久属性或字段的映射列。如果未指定列注解，则应用默认值。
     */
    @GeneratedValue(generator = "pk")
    @Column(name = "id")
    private int id;
    @Column(name = "username")
    private String username;
    @Column(name = "password")
    private String password;
    @Column(name = "name")
    private String name;
    @Column(name = "phone")
    private String phone;
    /*
    Temporal注解：
    必须为类型为的永久字段或属性指定此批注java.util.Date文件以及java.util.Calendar文件. 只能为这些类型的字段或属性指定。
    时态注解可与基本注解、Id注解或ElementCollection注解一起使用（当元素集合值是这种时态类型时）。
     */
    @Temporal(TemporalType.DATE)
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Column(name = "time")
    private Date time;
    /**
     * Transient注解：
     * 它指明了该属性不会被映射到数据表中某一列，而只是作为一个属性被定义在实体类中
     */
    @Transient
    private String phoneCode;
    /*
    一对多关系 (User vs Role)

    OneToMany注解：
    指定具有一对多 多重性的多值关联。
    如果使用泛型定义集合以指定元素类型，则无需指定关联的目标实体类型；否则必须指定目标实体类。
    如果关系是双向的，则必须使用mappedBy元素指定作为关系所有者的实体的关系字段或属性。
    OneToMany注解可以在实体类中包含的可嵌入类中使用，以指定与实体集合的关系。
    当集合是java.util.Map文件，cascade元素和orninRemoval元素应用于映射值。

    Fetch注解：
    定义用于给定关联的获取策略。

    JoinTable注解：
    指定关联的映射。它应用于关联的拥有方。
    联接表通常用于多对多和单向一对多关联的映射。它还可用于映射双向多对一/一对多关联、单向多对一关系和一对一关联（双向和单向）。
    当连接表用于将关系映射到关系所属方的可嵌入类时，包含实体而不是可嵌入类被视为关系的所有者。
    如果缺少JoinTable注解，则应用注解元素的默认值。联接表的名称假定为使用下划线连接在一起的关联主表的表名（首先是拥有方）。

    JoinColumn注解：
    指定用于连接实体关联或元素集合的列。如果JoinColumn注解本身为默认值，则假定为单个连接列，并应用默认值。
    */
    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    //@Fetch(FetchMode.SELECT)
    //中间表名,当前对象id在中间表的列名,关联的另一个表在中间表的列名
    @JoinTable(name = "ur", joinColumns = {@JoinColumn(name = "userid")}, inverseJoinColumns = {@JoinColumn(name = "roleid")})
    private Set<Role> roles;

    /**
     * 用户的权限集， 默认需要添加ROLE_ 前缀
     * 用户的加密后的密码， 不加密会使用{noop}前缀
     * 应用内唯一的用户名
     * org.springframework.security.core.userdetails.User 可以自行实现扩展以存储更多的用户信息。
     * <p>
     * Override注解：
     * 指示方法声明旨在重写父类型中的方法声明。如果使用此注解类型对方法进行注解，则需要编译器生成错误消息，除非至少满足以下条件之一：
     * 该方法重写或实现在父类型中声明的方法。
     * 该方法具有一个重写的签名，该签名等同于在对象中声明的任何公共方法的签名。
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        //权限集合
        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        //遍历角色
        for (Role role : getRoles()) {
            //添加查询到的角色
            grantedAuthorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        //返回结果
        return grantedAuthorities;
    }

    //账户是否过期
    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    //账户是否锁定
    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    //凭证是否过期
    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    //用户是否可用
    @Override
    public boolean isEnabled() {
        return false;
    }
}